Replace in RichText

Hello,

For me, it seems to be a very simple problem, but somehow i just don't get it to work. We want to use DXL to add some "Word-like/Latex" Features to DOORS. To make it quick: we define some wildcards within the Object-Text that are being replaced by a DOORS DXL script. For example: we write 'Table This is some table description' which should be replaced by 'Table 2: This is some table description' where 2 is determined by the DXL script; another example would be '134 depicts the problem' which is replaced by 'Figure 2 depicts the problem' where 134 points to requirement 134 which contains a figure and the DXL script replaces the 134 accordingly. So much for the background.
It all comes down to a simple problem:
I'd like to replace a string in the object text with another string. As simple as this. When dialing with plain text, it all works perfectly. But when dealing with RTF it gets hell, because we want the replacement independent of the formatting (like search and replace in Word).

We tried several approaches:
A) use findRichText/replaceRichText:

while(findRichText(s, searchFor, o, l, true))
{
s = replaceRichText(s, o, l, replacement);
}

Does not work: o and l returned by findRichText include formatting characters of the RTF, replaceRichText requires indices related to the string without formatting characters. (C'mon IBM, what is this ?)

Besides findRichText does not work if formatting characters are in between: if s contains Table where #Table is bold, findRichText does not find 'Table'. Ok, this could make sense, since from a RTF point of view "Table' and 'Table' where #Table is bold are not the same. But in this case, findRichText is useless when looking for content only. I mean, if I search in word, I also search content, not format.

B) use replaceRichText and get indices from plain text:

string makePlain(string s)
{
string outStr = "";
RichText rt;
RichTextParagraph rtp;

for rtp in s do
{
for rt in rtp do
{
outStr = outStr rt.text;
}
}

return outStr;
}

string plain = makePlain(s, plain);
while(findPlainText(plain, searchFor, o, l, true))
{
s = replaceRichText(s, o, l, replacement);

plain = makePlain(s);
}

Does not work: makePlain works ok, but somehow the whole code makes DOORS crash with some exception.

Any ideas ?

Thanks,
Philipp
SystemAdmin - Wed Apr 03 03:58:59 EDT 2013

Re: Replace in RichText
SystemAdmin - Wed Apr 03 05:19:05 EDT 2013

Hello

If you want to operate on complex rich text structures, there is only one possibility. You have to write your onw code.

One example may be: http://nrtftree.sourceforge.net/

And here the developer has stoppen 2009 in Version 0.3.

-> Small groups of developers
Small groups of useres

Best regards
Wolfgang

Re: Replace in RichText
SystemAdmin - Wed Apr 03 05:35:21 EDT 2013

SystemAdmin - Wed Apr 03 05:19:05 EDT 2013
Hello

If you want to operate on complex rich text structures, there is only one possibility. You have to write your onw code.

One example may be: http://nrtftree.sourceforge.net/

And here the developer has stoppen 2009 in Version 0.3.

-> Small groups of developers
Small groups of useres

Best regards
Wolfgang

So you're essentially stating, that DOORS offers only functions for RTF modification that are so poor that a simple format-ignoring search for text strings in an RTF string cannot be realized ?

Oh my god !

Re: Replace in RichText
SystemAdmin - Wed Apr 03 05:36:26 EDT 2013

SystemAdmin - Wed Apr 03 05:19:05 EDT 2013
Hello

If you want to operate on complex rich text structures, there is only one possibility. You have to write your onw code.

One example may be: http://nrtftree.sourceforge.net/

And here the developer has stoppen 2009 in Version 0.3.

-> Small groups of developers
Small groups of useres

Best regards
Wolfgang

Besides, solution B) would work if there wouldn't be this crash. Any ideas for that ? Does the code work in your DOORS installation ?

Re: Replace in RichText
SystemAdmin - Wed Apr 03 07:21:21 EDT 2013

SystemAdmin - Wed Apr 03 05:36:26 EDT 2013
Besides, solution B) would work if there wouldn't be this crash. Any ideas for that ? Does the code work in your DOORS installation ?

No it does not, because I do not have the string.

What do you think? Shall I create a testcase four your problem?

Re: Replace in RichText
SystemAdmin - Wed Apr 03 07:30:54 EDT 2013

SystemAdmin - Wed Apr 03 07:21:21 EDT 2013
No it does not, because I do not have the string.

What do you think? Shall I create a testcase four your problem?

What do you mean with
 

string plain = makePlain(s, plain);

Re: Replace in RichText
OurGuest - Wed Apr 03 07:33:05 EDT 2013

The correct way to process richtext is hard to find by looking in the help file but can be found if you look for: RichTextParagraph type properties.

If you process richtext as described in this section of the help file you can entertain yourself for hours.

Re: Replace in RichText
SystemAdmin - Wed Apr 03 08:56:32 EDT 2013

SystemAdmin - Wed Apr 03 07:30:54 EDT 2013

What do you mean with
 

string plain = makePlain(s, plain);

should be "string plain = makePlain(s);"

Re: Replace in RichText
SystemAdmin - Wed Apr 03 09:42:58 EDT 2013

Hello Philipp,
You have highlighted one of the pitfalls of DOORS in that it doesn't handle richtext very well and there are not much help to deal with it.
I've had to do similar types of dxl scripts and the only way that I found how to do it is by brute force.
Try searching for the text you want to find using plain text and if found, then do a character by character search using stringOf(richtext). The trick here is to ignore the richtext codes in the search loop.

Another way to make the replacement with removing any rich text codes that are inside the text. The idea here is that if your looking for a single word like "Table" then the users should not have rich text codes in the middle of it. If they do, then it is probably just ending of the same rich text code and starting it again, so if these are removed, the RTF should still be fully formed. In other words, if you see ...Ta}{ble... then the two brackets can be eliminated. A little detective work is needed to see what kinds of things that could happen.

Hope this helps a little. Many a dxl'er has gone mad in trying to work with rich text.

Greg

Re: Replace in RichText
SystemAdmin - Wed Apr 03 11:11:39 EDT 2013

SystemAdmin - Wed Apr 03 09:42:58 EDT 2013
Hello Philipp,
You have highlighted one of the pitfalls of DOORS in that it doesn't handle richtext very well and there are not much help to deal with it.
I've had to do similar types of dxl scripts and the only way that I found how to do it is by brute force.
Try searching for the text you want to find using plain text and if found, then do a character by character search using stringOf(richtext). The trick here is to ignore the richtext codes in the search loop.

Another way to make the replacement with removing any rich text codes that are inside the text. The idea here is that if your looking for a single word like "Table" then the users should not have rich text codes in the middle of it. If they do, then it is probably just ending of the same rich text code and starting it again, so if these are removed, the RTF should still be fully formed. In other words, if you see ...Ta}{ble... then the two brackets can be eliminated. A little detective work is needed to see what kinds of things that could happen.

Hope this helps a little. Many a dxl'er has gone mad in trying to work with rich text.

Greg

Your tips are very appreciated.

I already had the feeling that it comes down to such a "I have to do it manually / myself" solution. I was hoping that DXL offers some more RTF-editing capabilities or that I'm overseeing something obvious. But it seems like I have to write the code by my own. Thinking of that I have the feeling that all this string-parsing stuff will require quite some resources and get too slow to be used in e.g. a layout-DXL script. So even when programming the required RTF capabilities they might end up useless.

I have to check if the effort is worth it or - as you already wrote - if we solve it by process rules (e.g. do not use formatting 'inside' tags to replace or do not use rtf at all :-( ).

Re: Replace in RichText
SystemAdmin - Wed Apr 03 11:20:48 EDT 2013

SystemAdmin - Wed Apr 03 11:11:39 EDT 2013
Your tips are very appreciated.

I already had the feeling that it comes down to such a "I have to do it manually / myself" solution. I was hoping that DXL offers some more RTF-editing capabilities or that I'm overseeing something obvious. But it seems like I have to write the code by my own. Thinking of that I have the feeling that all this string-parsing stuff will require quite some resources and get too slow to be used in e.g. a layout-DXL script. So even when programming the required RTF capabilities they might end up useless.

I have to check if the effort is worth it or - as you already wrote - if we solve it by process rules (e.g. do not use formatting 'inside' tags to replace or do not use rtf at all :-( ).

Another solution may be to copy the data to word and to perform the actions in this programm. This can be done in the backound without any user activity. But the user should have a word installation on his client.

Perhaps the writer of open office may be a solution too, I do not know.

Re: Replace in RichText
llandale - Wed Apr 03 12:30:29 EDT 2013

I came up with this solution several years ago. It works ineffeciently but it works; and yes it needs to be improved (it's "on the list"). The key here is that function "cutRichText()" takes input offset values that are relative to the corresponding RAW text. So we search the raw text then cut-up and re-assemble the rich-text.

These are the main excerpt functions, you'll need to write your own "fGetOffset" to find the sub-string in the raw text.

Someone want to write these more "elegantly" they'll get the gold star.

-Louie

//*********************
string    fStripRT(string InString)
{     // Legacy
        return(plainText(InString))
}     // end fStripRT(string)
 
//*****************
string  fExtractRT(string TextRich, int StartLoc, EndLoc, LenRaw)
{     // Extract the portion of the Rich Text string in the TextRich
        //   as identified by the starting and ending locations.
        // Preserve the TextRich's RichText markup
 
        // StartLoc and EndLoc and LenRaw MUST be relative to the equivalent RAW text,
        //    perhaps by calling Loc = fGetOffset(TextRaw, SearchString).
 
        // Strategy: only mechanism for extracting out of a rich text chunk is using cutRichText().
        //      Thus, we cut the part after EndLoc, then cut the part before StartLoc, leaving what we want.
 
        string  OutText
        if (EndLoc   < StartLoc      or
            EndLoc   > LenRaw        or
            StartLoc < 0)            return("")    // Program Input Error
 
        if (EndLoc == LenRaw)
             OutText = TextRich
        else OutText = cutRichText(TextRich, EndLoc+1, LenRaw)          // Strip Ending
        // print ":::: Extract, TextRich, OutText = >>" TextRich "<<<   >>>" OutText "<<\n"
        if (StartLoc > 0)
             OutText = cutRichText(OutText, 0,  StartLoc-1)             // Strip Beginning
        // print ">>>>>> Extract: In/Start/End/Tot/Out = >>>" TextRich "<<<  " StartLoc "  " //-
        //             EndLoc "  " LenTotal " >>>" OutText "<<<\n"
        return(OutText)
}     // end fExtractRT()
 
//****************************************************
string  fReplaceRawText(string InRich, StringFrom, StringTo, bool in_MatchCase)
{     // Replace all occurrences of the raw StringFrom to the raw StringTo in the
        //    Rich Text string InRich.  RichText markup in InRich is ignored but preserved.
        // Note: Even for simple RichText strings, this function returns a very large string
        //    featuring lots of RTF header info.  User may want to issue "removeUnlistedRichText"
        //    after calling this function.
        // Developers Note: This is a little tricky since some StringFrom may be part of the Markup,
        //    but needs to be ignored.
        // Strategy: Get the Raw representation of the string.  Search for the StringFrom.
        //    if found, build a new RichText string composed of the part before the found location,
        //    the StringTo, and the part after the found location.  That last part must be
        //    recursively searched before its appended.
 
 
        if (null InRich or null StringFrom) return("")
 
        string  InRaw, FirstPart, LastPart
        int     LenFrom, LocFrom, LenRaw
 
        InRaw   = fStripRT(InRich)
        LocFrom = fGetOffset(InRaw, StringFrom, in_MatchCase)
        if (LocFrom < 0) return(InRich)      // StringFrom not found, nothing to do
        LenRaw  = length(InRaw)
        LenFrom = length(StringFrom)
 
        if (LocFrom == 0)
             FirstPart = ""   // Found in the first position.
        else FirstPart = fExtractRT(InRich, 0, LocFrom-1, LenRaw)
 
        if (LocFrom+LenFrom-1 >= LenRaw)
             LastPart  = ""   // Found as last position(s) in the string
        else
        {    LastPart = fExtractRT(InRich, LocFrom+LenFrom, LenRaw-1, LenRaw)
             LastPart = fReplaceRawText(LastPart, StringFrom, StringTo, in_MatchCase)   // ** RECURSIVE **
        }
//      print "**LenRich/LenRaw =\t" (length(InRich)) "\t" LenRaw "\n"
//      print "**LocFrom   =\tSearching for \t<">, found at \t" LocFrom   "\n"
//      print "**FirstPart =\t" FirstPart "\n"
//      print "**LastPart  =\t" LastPart  "\n"
        return(fConCat(FirstPart, StringTo, LastPart))
}     // end fReplaceRawText()

Re: Replace in RichText
TomWa - Mon Sep 04 05:41:15 EDT 2017

Hi Guys,

i also came accross the problems connected with replacing inside richtext and the entirely useless implementation of "findRichText".
Thus i also focused on the only (at first glance) useful function "cutRichText".

But cutRichText has some major problems for my usecase too. It does not preserve the original markups, but digests them to its own liking.
I already merged multiple rtf strings for viewing in a layout dxl and this worked quite fine so far.

A example rtf output looks like:

----------------------raw String: ------------------------
Colored Heading
text 1 text 2 text 3

where text 1..3 are rich text fragments enclosed by brackets "{}" to restrict their scope.

----------------------rtf string before cutRichText ------------------------
\{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1031\{\\fonttbl\{\\f0\\fnil\\fcharset0 Tahoma;\}\{\\f1\\fnil\\fcharset2 Symbol;\}\}
\{\\colortbl;\\red255\\green100\\blue50;\\red0\\green0\\blue255;\\red140\\green50\\blue140;\\red50\\green140\\blue140;\\red0\\green128\\blue255;\\red255\\green0\\blue0;\\red255\\green100\\blue0;\\red255\\green200\\blue0;\\red128\\green128\\blue128;\}
\{\\viewkind4\\pard\\f0\{\\sub\\cf2 Colored Heading\}\{\\cf0\\par text 1 text 2 text 3\}\}\}

Desired action:
replace rtf version of "text 2" with "\\highlight8text 2\\highlight "

 

After digesting with cutRichText (close to your original dxl, louie) to insert highlight it looks this/should look like this:

----------------------desired rtf string ------------------------
\{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1031\{\\fonttbl\{\\f0\\fnil\\fcharset0 Tahoma;\}\{\\f1\\fnil\\fcharset2 Symbol;\}\}
\{\\colortbl;\\red255\\green100\\blue50;\\red0\\green0\\blue255;\\red140\\green50\\blue140;\\red50\\green140\\blue140;\\red0\\green128\\blue255;\\red255\\green0\\blue0;\\red255\\green100\\blue0;\\red255\\green200\\blue0;\\red128\\green128\\blue128;\}
\{\\viewkind4\\pard\\f0\{\\sub\\cf2 Colored Heading\}\{\\cf0\\par text 1 \\highlight8text 2\\highlight text3\}\}\}


 

----------------------rtf string after cutRichText with Replacement of "text 2" with "\\highlight8text 2\\highlight" ------------------------
\{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1031\{\\fonttbl\{\\f0\\fnil\\fcharset0 Tahoma;\}\}
\{\\colortbl ;\\red0\\green0\\blue255;\}
\{\\*\\generator Msftedit 5.41.21.2510;\}\\viewkind4\\uc1\\pard\\cf1\\sub\\f0\\fs24 Colored Heading\\cf0\\nosupersub\\par
text 1}
\\highlight8text 2\\highlight \{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1031\{\\fonttbl\{\\f0\\fnil\\fcharset0 Tahoma;\}\}
\{\\*\\generator Msftedit 5.41.21.2510;\}\\viewkind4\\uc1\\pard\\f0\\fs24
text 3
 \\par\}

 


The digesting as such would (if done well) even improve the markups, but it seems to be done in a bad way:
 -Every call of cutRichText (there are multiple) generates a "standalone" rtf string (ok that may be tolerable by itself, although blowing the rtf up):
  -it inserts a new rtf header
  -it ends the markup scope with a closing "}"
 -The inserted headers/footers cannot be removed again with subsequent calls of cutRichText
 -The original color and font tables are not preserved, so elements not used in the RETURNED string (not the input string) are discarded and a re-numbering is done
  -> if cutRichText is used several times, each time table elements are cut out which are not used in the scope
  -> the color-numbers and (probably font-numbers) are re-sorted so are not usable anymore
 
->The highlight part works not due to the table maybe cut in a way the color is not even contained anymore or has a wrong number
->The highlight part works not because the table scope is ended by a closing bracket, this can be removed by a buffer shortening after each cutRichText and adding the bracket afterwards:
 

 length(bsz, length(bsz)-5) //remove CR}CR -> 5 chars... why is there a CR inserted by cutRichText ?
 bsz += "mid text"
 bsz += "}"

 

Not sure if theres a straight forward solution, any digestion with cutRichText might remove the highlight color from the color table if not already contained in the parameter string.
Introducing a empty Highlight in the parameter text seems feasible but dumb.
 

Primarily wanted to point users of cutRichText to the pitfalls connected with it.

If anyone comes along an elegant solution... very welcome.

 

Greets Tom